/**
 * 
 */
package reit.ass3;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.log4j.Logger;


/**
 * The Class RunnableMaintenanceRequest, pulls damage report from managment and fixes assets
 *
 * @author Mordechai
 */
class RunnableMaintenanceRequest implements Runnable {

	/** The repair tool information. */
	private final Hashtable<String, RepairToolInformation> fRepairToolInformation;

	/** The repair material information. */
	private final Hashtable<String, RepairMaterialInformation> fRepairMaterialInformation;

	/** The warehouse. */
	private Warehouse fWarehouse;

	/** The asset. */
	private Asset fAsset;

	/** The number of  open requests. */
	private AtomicInteger fOpenRequests;

	/** The all customer groups end stay. */
	private AtomicInteger fAllCustomerGroupsEndStay;

	/** The m damage report. */
	private DamageReport mDamageReport;

	/** The management. */
	private Management fManagement;

	/** The m sleep time. */
	private long mSleepTime;

	/** The logger. */
	private Logger fLogger;

	private AtomicInteger fNumWorkingClerks;
	/** The Constant SLEEP_MULTIPLIER. (miliseconds to seconds) */
	public static final int SLEEP_MULTIPLIER = 1;

	/**
	 * Instantiates a new runnable maintenance request.
	 *
	 * @param tWarehouse the t warehouse
	 * @param tManagement the t management
	 * @param openRequests the open requests
	 * @param tRepairToolInformation the t repair tool information
	 * @param tRepairMaterialInformation the t repair material information
	 * @param allCustomerGroupsEndStay the all customer groups end stay
	 */
	public RunnableMaintenanceRequest(
			final Warehouse tWarehouse,
			final Management tManagement,
			final AtomicInteger openRequests,
			final Hashtable<String, RepairToolInformation> tRepairToolInformation,
			final Hashtable<String, RepairMaterialInformation> tRepairMaterialInformation,
			final AtomicInteger allCustomerGroupsEndStay,AtomicInteger numWorkingClerks) {
		this.fLogger = Logger.getLogger(RunnableMaintenanceRequest.class);
		fRepairMaterialInformation = tRepairMaterialInformation;
		fRepairToolInformation = tRepairToolInformation;
		this.fOpenRequests = openRequests;
		this.fWarehouse = tWarehouse;
		this.fManagement = tManagement;
		this.fAllCustomerGroupsEndStay = allCustomerGroupsEndStay;
		this.fNumWorkingClerks = numWorkingClerks;
	}

	/**
	 * starts the mainteanace worker
	 */
	@Override
	public void run() {
		fLogger.info("Maintenance Worker On The Job");
		this.mDamageReport = waitForCustomerGroupsToFinishStay();
		if ((this.mDamageReport) != null) {
			this.fNumWorkingClerks.incrementAndGet();
			this.fAsset = this.mDamageReport.getAsset();
			List<RepairObjectInterface> toRelease = new ArrayList<RepairObjectInterface>();
			toRelease.addAll(acquireToolsAndMaterials());

			try {
				this.fLogger.info(MyStringBuilder.buildString(
						"Maintnance worker is repairing",
						this.fAsset.getName(),
						" for the next " + Long.toString(mSleepTime)));
				Thread.sleep(mSleepTime);
			} catch (InterruptedException e) {
				this.fLogger.error("maintnance worker failed to repair");
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			fLogger.info(MyStringBuilder.buildString("Maintaenance Worker Returning Tools"));
			releaseTools(toRelease);
			fixAsset();

			//this.fOpenRequests.decrementAndGet();
			this.fNumWorkingClerks.decrementAndGet();
			this.fManagement.returnAsset(this.fAsset);
			this.fLogger.info(MyStringBuilder.buildString("maintnace on ",
					this.fAsset.getName(), "is Done!"));
		}
		this.fLogger.info("Maintnace worker is Done");
	}

	//also updates mSleepTime
	/**
	 * Acquire tools and materials.
	 *
	 * @return list of RepairTools to return
	 */
	private List<RepairObjectInterface> acquireToolsAndMaterials() {
		List<Collection<AssetContent>> assetContents = Parser.mapAsList(this.fAsset.getAssetContents().asMap());
		HashMap<String, RepairObjectInterface> tools = new HashMap<String, RepairObjectInterface>();
		LinkedList<RepairObjectInterface> materials = new LinkedList<RepairObjectInterface>();
		for(Collection<AssetContent> tassetContents : assetContents){
			for(AssetContent tAssetContent : tassetContents){
				materials.addAll(this.fRepairMaterialInformation.get(tAssetContent.getName()).getMaterials());
				for (RepairTool tRepairTool : this.fRepairToolInformation.get(tAssetContent.getName()).getTools()){
					if(tools.get(tRepairTool.getName()) == null){
						//tools is not in tool list yet
						tools.put(tRepairTool.getName(),new RepairTool(tRepairTool));
					}
					else if (tools.get(tRepairTool.getName()).getQuantity() < tRepairTool.getQuantity()){
						//increase by differnce needed 
						tools.get(tRepairTool.getName()).increaseQuantityBy(new RepairTool(tRepairTool.getName(),tRepairTool.getQuantity() - tools.get(tRepairTool.getName()).getQuantity()));
					}
				}
				// update sleep time
				this.mSleepTime = (long) (this.mSleepTime + (tAssetContent
						.getRepairCostMultiplier() * (Asset.MAX_HEALTH - tAssetContent
								.getHealth())));
				this.mSleepTime = mSleepTime*RunnableMaintenanceRequest.SLEEP_MULTIPLIER;
			}
		}
		this.fManagement.checkOutFromWarehouse(getToolsAsList(tools.values()));
		this.fManagement.checkOutFromWarehouse(materials);
		this.fLogger.info("checking out of tools complete");
		return getToolsAsList(tools.values());
	}

	/**
	 * Gets the tools as list.
	 *
	 * @param collection the collection
	 * @return the tools as list
	 */
	private List<RepairObjectInterface> getToolsAsList(
			final Collection<RepairObjectInterface> collection) {
		List<RepairObjectInterface> result = new ArrayList<RepairObjectInterface>();
		for(RepairObjectInterface r : collection){
			result.add(new RepairTool(r));
		}
		return result;
	}

	/**
	 * Release tools.
	 *
	 * @param toRelease the to release
	 */
	private void releaseTools(final List<RepairObjectInterface> toRelease) {
		this.fWarehouse.checkIn(toRelease);
	}

	/**
	 * Fix asset.
	 */
	private void fixAsset() {

		synchronized (mDamageReport.getAsset()) {

			Map<String, Collection<AssetContent>> assetContents = mDamageReport
					.getAsset().getAssetContents().asMap();
			for (String key : mDamageReport.getAsset().getAssetContents()
					.keySet()) {
				Collection<AssetContent> assetContent = assetContents.get(key);
				for (AssetContent a : assetContent) {
					if (a.getHealth() < Management.ASSET_MAX_DAMAGE_BEFORE_REPAIR) {
						a.fix();
					}
				}
				mDamageReport.getAsset().notifyAll();
			}
			mDamageReport.getAsset().setStatusTo(Status.AVAILABLE);
			this.fLogger.info(MyStringBuilder.buildString("asset ",
					this.fAsset.getName(), " was Fixed and is now AVAILABLE"));
		}

	}

	/* * @description guarded block for checking that rentalrequst fufilled
	 * @category GuardedBlock
	 * */
	/**
	 * Wait for customer groups to finish stay.
	 *
	 * @return the damage report
	 */
	private DamageReport waitForCustomerGroupsToFinishStay() {
		while (fAllCustomerGroupsEndStay.get() == 0) {
			try {
				return this.fManagement.takeDamageReport();
			} catch (IllegalMonitorStateException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		this.fLogger.info("Customer Manager received RequestChange.");
		return null;
	}
}
